/******************************************************************************

    Simple Authentication and Authorisation System (SAAS)

    pam_saas.c

    Copyright (C) 2011  Josh Goes


    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    If you require a mailing address for this software, please send your
    mail to the following address:
        SAAS
        GPO Box 2973
        BRISBANE QLD 4001
        AUSTRALIA

******************************************************************************/


/* pam_saas module */

#include "saas.h"

#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#define PAM_SM_PASSWORD
#define PAM_SM_SESSION

#define MODULE_NAME  "pam_saas"

#define MAX_USERNAME_LEN		256
#define MAX_PASSWORD_LEN		256

// PAM argument flags
#define PAM_DEBUG_ARG                                               0x0001
#define PAM_DUMP_ARG                                                0x0002
#define PAM_NO_TIME_ARG                                             0x0004
#define PAM_USE_FPASS_ARG                                           0x0008
#define PAM_TRY_FPASS_ARG                                           0x0010


#include <security/pam_modules.h>
#include <security/pam_ext.h>
#include <security/_pam_macros.h>
#include <syslog.h>

#define	_(x)	(x)


static int obtain_authtok( pam_handle_t *pamh )
{
	char *resp;
	const void *item;
	int retval;

	retval = pam_prompt( pamh , PAM_PROMPT_ECHO_OFF , &resp , _("Password: ") );

	if ( retval != PAM_SUCCESS )
		return retval;

	if ( resp == NULL )
		return PAM_CONV_ERR;

	// set the auth token
	retval = pam_set_item( pamh , PAM_AUTHTOK , resp );

	// clean it up
	_pam_overwrite( resp );
	_pam_drop( resp );

	if ( ( retval != PAM_SUCCESS ) || ( retval = pam_get_item( pamh , PAM_AUTHTOK , &item ) ) != PAM_SUCCESS )
		return retval;

	return retval;
}



static int get_creds( pam_handle_t *pamh , int flags , char *user , char *pass )
{
	const char *username;
	const void *password;
	int retval;

	// Get the username
	retval = pam_get_user(pamh, &username, NULL);
	if ((retval != PAM_SUCCESS) || (!username))
	{
		pam_syslog(pamh, LOG_ERR, "can not get the username");
		return PAM_SERVICE_ERR;
	}

	if ((flags & PAM_USE_FPASS_ARG) == 0 && (flags & PAM_TRY_FPASS_ARG) == 0)
	{
		// Converse to obtain a password
		retval = obtain_authtok(pamh);
		if (retval != PAM_SUCCESS)
		{
			pam_syslog(pamh, LOG_ERR, "can not obtain password from user");
			return retval;
		}
	}

	// Check if we got a password
	retval = pam_get_item(pamh, PAM_AUTHTOK, &password);
	if (retval != PAM_SUCCESS || password == NULL)
	{
		if ((flags & PAM_TRY_FPASS_ARG) != 0)
		{
			// Converse to obtain a password
			retval = obtain_authtok(pamh);
			if (retval != PAM_SUCCESS)
			{
				pam_syslog(pamh, LOG_ERR, "can not obtain password from user");
				return retval;
			}
			retval = pam_get_item(pamh, PAM_AUTHTOK, &password);
		}
		if (retval != PAM_SUCCESS || password == NULL)
		{
			pam_syslog(pamh, LOG_ERR, "can not recover user password");
			return PAM_AUTHTOK_RECOVERY_ERR;
		}
	}
	const char *pass2 = password;
	strcpy( user , username );
	strcpy( pass , pass2 );

	return 0;
}



static int
pam_arg_parse (pam_handle_t *pamh, int argc, const char **argv,
		const char **confpath)
{
	int flags = 0;

	*confpath = SAAS_CONF_PATH_DEFAULT;

	// step through arguments
	for ( ; argc-- > 0 ; ++argv )
	{
		// generic options
		if (!strcmp(*argv,"debug"))
			flags |= PAM_DEBUG_ARG;
		else if (!strcasecmp(*argv, "dump"))
			flags |= PAM_DUMP_ARG;
		else if (!strcasecmp(*argv, "use_first_pass"))
			flags |= PAM_USE_FPASS_ARG;
		else if (!strcasecmp(*argv, "try_first_pass"))
			flags |= PAM_TRY_FPASS_ARG;
		else if (!strncasecmp(*argv,"conf=", 5))
		{
			*confpath = (*argv) + 5;
			if (**confpath == '\0')
			{
				*confpath = SAAS_CONF_PATH_DEFAULT;
				pam_syslog(pamh, LOG_WARNING,
					"conf= specification missing argument - using default \"%s\"", SAAS_CONF_PATH_DEFAULT );
			}
		}
		else
		{
			pam_syslog(pamh, LOG_WARNING, "skipping unknown option: %s", *argv);
		}
	}
	return flags;
}




//
// functions that PAM calls.
//

PAM_EXTERN int
pam_sm_authenticate( pam_handle_t *pamh , int flags ,
	int argc , const char **argv )
{
	char user[MAX_USERNAME_LEN];
	char pass[MAX_PASSWORD_LEN];
	const char *confpath = NULL;
	int optflags;

	// try to parse arguments
	optflags = pam_arg_parse( pamh , argc , argv , &confpath );
	if ( confpath == NULL )
	{
		pam_syslog( pamh , LOG_ERR , "SAAS conf file path not set. Try setting `conf=/etc/saas.conf' argument." );
		return PAM_SERVICE_ERR;
	}

	// get the user & pass from pam
	if ( optflags & PAM_DEBUG_ARG )
		pam_syslog( pamh , LOG_DEBUG , "Trying to get username and password from PAM..." );
	int retval = get_creds( pamh , optflags , user , pass );
	if ( retval != 0 )
		return retval;


	if ( optflags & PAM_DEBUG_ARG )
		pam_syslog( pamh , LOG_DEBUG , "Verifying user `%s'..." , user );

	// Now that we've got a password we can use. Try to parse the config file.
	if ( optflags & PAM_DEBUG_ARG )
		pam_syslog( pamh , LOG_DEBUG , "About to call saas_init() to set up SAAS instance." );
	saas_t  inst;
	uint16_t ret = saas_init( &inst , NULL , ( optflags & PAM_DEBUG_ARG ) ? LOG_DEBUG : LOG_WARNING , SAAS_LOG_PAM_SYSLOG , NULL , pamh );
	if ( ret != SAAS_OP_SUCCESS )
	{
		pam_syslog( pamh , LOG_ERR , "Error calling saas_init(). Error: (%d) %s" , ret , saaserror( ret ) );
		pam_syslog( pamh , LOG_ERR , "Trying to read conf file at `%s'" , confpath );
		return PAM_SERVICE_ERR;
	}
	if ( optflags & PAM_DEBUG_ARG )
		pam_syslog( pamh , LOG_DEBUG , "Successfully called saas_init()" );


	// Now perform an auth lookup
	uint32_t  pass_expire;
	if ( optflags & PAM_DEBUG_ARG )
		pam_syslog( pamh , LOG_DEBUG , "About to call saas_authenticate() to authenticate user." );
	ret = saas_authenticate( &inst , user , pass , &pass_expire );
	if ( optflags & PAM_DEBUG_ARG )
		pam_syslog( pamh , LOG_DEBUG , "saas_authenticate() returned: (%d) %s" , ret , saaserror( ret ) );

	switch ( ret )
	{
		case SAAS_OP_SUCCESS:
			// auth looks good.
			pam_syslog( pamh , LOG_NOTICE , "SAAS: user `%s' successfully authenticated." , user );
			return PAM_SUCCESS;

		case SAAS_OP_INVALID_PASS:
			// incorrect password
			pam_syslog( pamh , LOG_WARNING , "SAAS: failed attempt: user `%s': invalid password" , user );
			return PAM_AUTH_ERR;

		case SAAS_OP_USER_NOT_FOUND:
			// the user does not exist in the database
			if ( optflags & PAM_DEBUG_ARG )
				pam_syslog( pamh , LOG_INFO , "User `%s' not found in the database." , user );
			return PAM_USER_UNKNOWN;

		default:
			// we don't know anything about this return value
			pam_syslog( pamh , LOG_ERR , "SAAS: Internal module error (ret = %d (%s), user = `%s')" , ret , saaserror( ret ) , user );
			return PAM_SERVICE_ERR;
	}

	// should not be reached
	return PAM_IGNORE;
}



PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh , int flags ,
		   int argc , const char **argv )
{
	return PAM_IGNORE;
}

PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags ,
		 int argc, const char **argv)
{
/*
	const char *username;
	const char *database = NULL;
	const char *cryptmode = NULL;
	int retval = PAM_AUTH_ERR, ctrl;

	// parse arguments
	ctrl = _pam_parse(pamh, argc, argv, &database, &cryptmode);

	// Get the username
	retval = pam_get_user(pamh, &username, NULL);
	if ((retval != PAM_SUCCESS) || (!username)) {
		pam_syslog(pamh, LOG_ERR,"can not get the username");
		return PAM_SERVICE_ERR;
	}

	// Now use the username to look up password in the database file
	retval = user_lookup(pamh, database, cryptmode, username, "", ctrl);
	switch (retval) {
		case -2:
		// some sort of system error. The log was already printed
		return PAM_SERVICE_ERR;
	case -1:
		// incorrect password, but we don't care
		// FALL THROUGH
	case 0:
		// authentication succeeded. dumbest password ever.
		return PAM_SUCCESS;
	case 1:
		// the user does not exist in the database
		return PAM_USER_UNKNOWN;
		default:
		// we don't know anything about this return value
		pam_syslog(pamh, LOG_ERR,
			   "internal module error (retval = %d, user = `%s'",
			   retval, username);
			return PAM_SERVICE_ERR;
	}
*/
	return PAM_SUCCESS;
}



PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
	return PAM_IGNORE;
}


PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
	return PAM_IGNORE;
}


PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
	return PAM_IGNORE;
}


#ifdef PAM_STATIC


struct pam_module _pam_saas_modstruct = {
	 "pam_saas",
	 pam_sm_authenticate,
	 pam_sm_setcred,
	 pam_sm_acct_mgmt,
	 pam_sm_open_session,
	 pam_sm_close_session,
	 pam_sm_chauthtok,
};

#endif



